/*
 * XUITestCase.java
 * 
 * Copyright (c) 2007
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.xfc.testing;

import java.awt.AWTException;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dialog;
import java.awt.Frame;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Window;
import java.awt.event.InputEvent;

import javax.swing.JComponent;
import javax.swing.JDialog;

import org.jmock.MockObjectTestCase;
import org.jmock.core.Constraint;
import org.xfc.util.XUtils;

/**
 * Abstract parent class for UI related testcases.
 * 
 * @author Devon Carew
 */
public abstract class XUITestCase
	extends MockObjectTestCase
{
	private static final int MAX_WAIT_TIME = 30 * 1000;
	
	private static Robot awtRobot;
	
	static
	{
		try
		{
			awtRobot = new Robot();
			awtRobot.setAutoWaitForIdle(true);
			//awtRobot.setAutoDelay(50);
		}
		catch (AWTException e)
		{
			throw new RuntimeException(e);
		}
	}
	
	
	public XUITestCase()
	{
		super();
	}
	
	public XUITestCase(String name)
	{
		super(name);
	}
	
	protected JDialog findDialog(final String title)
	{
		if (title == null)
			return null;
		
		return (JDialog)locate(
			and(componentNameIs(title), and(isComponentShowing(), isA(JDialog.class))));
	}
	
	protected void dismissDialog(final JDialog dialog)
	{
		XUtils.invokeAndWait(new Runnable() {
			public void run() {
				dialog.dispose();
			}
		});
		
		waitForIdle();
	}
	
	protected Constraint exists(final String name)
	{
		return new Constraint() {
			public StringBuffer describeTo(StringBuffer buffer)
			{
				buffer.append("exists " + name);
				return buffer;
			}
			public boolean eval(Object object)
			{
				return locate(name) != null;
			}
		};
	}
	
	protected Constraint locateShowing(final String name)
	{
		return new Constraint() {
			public StringBuffer describeTo(StringBuffer buffer)
			{
				buffer.append("showing " + name);
				return buffer;
			}
			
			public boolean eval(Object object)
			{
				return locate(and(componentNameIs(name), isComponentShowing())) != null;
			}
		};
	}
	
	protected Component locate(String name)
	{
		return locate(componentNameIs(name));
	}
	
	protected Component locate(final Constraint constraint)
	{
		return (Component)XComponentVisitor.accept(new XComponentVisitor() {
			protected boolean visit(Component component)
			{
				if (constraint.eval(component))
				{
					setResult(component);
					setFinished(true);
				}
				
				return !isFinished();
			}
			
			protected boolean visit(Container container)
			{
				if (constraint.eval(container))
				{
					setResult(container);
					setFinished(true);
				}
				
				return !isFinished();
			}
			
			protected boolean visit(Frame frame)
			{
				if (constraint.eval(frame))
				{
					setResult(frame);
					setFinished(true);
				}
				
				return !isFinished();
			}
			
			protected boolean visit(Window window)
			{
				if (constraint.eval(window))
				{
					setResult(window);
					setFinished(true);
				}
				
				return !isFinished();
			}
		});
	}

	protected boolean waitFor(Constraint constraint)
	{
		return waitFor(constraint, null);
	}
	
	protected boolean waitFor(Constraint constraint, Object param)
	{
		long startTime = System.currentTimeMillis();
		
		while (!constraint.eval(param))
		{
			if ((System.currentTimeMillis() - startTime) > MAX_WAIT_TIME)
				return false;
			
			XUtils.sleepMillis(200);
		}
		
		return true;
	}
	
	protected void click(JComponent component)
	{
		Point 		absLocation = component.getLocationOnScreen();
		Rectangle 	bounds = component.getBounds();
		Point 		clickPoint = new Point(absLocation.x + bounds.width / 2, absLocation.y + bounds.height / 2);
		
		awtRobot.mouseMove(clickPoint.x, clickPoint.y);
		awtRobot.mousePress(InputEvent.BUTTON1_MASK);
		awtRobot.mouseRelease(InputEvent.BUTTON1_MASK);
		
		// Wait until the EDT is quiet again.
		waitForIdle();
	}
	
	protected void waitForIdle()
	{
		awtRobot.waitForIdle();
	}
	
	protected Constraint componentNameIs(final String name)
	{
		return new Constraint() {
			public boolean eval(Object object)
			{
				Component component = (Component)object;
				
				if (XUtils.safeEquals(name, component.getName()))
					return true;
				
				if (component instanceof Dialog)
				{
					if (XUtils.safeEquals(name, ((Dialog)component).getTitle()))
						return true;
				}
				
				if (component instanceof Frame)
				{
					if (XUtils.safeEquals(name, ((Frame)component).getTitle()))
						return true;
				}
				
				return false;
			}
			public StringBuffer describeTo(StringBuffer buffer)
			{
				buffer.append("name is " + name);
				return buffer;
			}
		};
	}
	
	protected Constraint isComponentShowing()
	{
		return hasProperty("showing", eq(true));
	}

	protected void type(JComponent component, String string)
	{
		// TODO: send the text in directly, or select the component and type the keys w/ the robot?
		
		if (!component.isFocusOwner())
		{
			component.requestFocusInWindow();
			
			waitFor(new Constraint() {
				public StringBuffer describeTo(StringBuffer buffer) {
					buffer.append("isFocusOwner");
					return buffer;
				}
				public boolean eval(Object object) {
					return ((Component)object).isFocusOwner();
				}
			}, component);
		}
		
		awtRobot.keyPress(0xFF & 'c');
		waitForIdle();
		awtRobot.keyRelease(0xFF & 'c');
		waitForIdle();
	}
	
}
